feat: add evaluate_flags/2 API for single-call flag evaluation#103
Open
feat: add evaluate_flags/2 API for single-call flag evaluation#103
Conversation
Introduce PostHog.FeatureFlags.evaluate_flags/2 returning an Evaluations
snapshot that powers branching across multiple flags and event enrichment
from a single /flags request. Pairs with PostHog.FeatureFlags.set_in_context/2
for the idiomatic Elixir capture-enrichment flow via the existing per-process
context, and with Evaluations.event_properties/1 for explicit one-off attach.
%PostHog.FeatureFlags.Result{} gains :id, :version, :reason, :request_id, and
:evaluated_at. The existing check/3, check!/3, and get_feature_flag_result/4
paths now attach $feature_flag_id, $feature_flag_version, $feature_flag_reason,
and $feature_flag_request_id to $feature_flag_called events when the response
provides them - needed for experiment exposure tracking.
Generated-By: PostHog Code
Task-Id: c6aa804c-618a-4229-b6a3-dc8c9ccff778
Contributor
posthog-elixir Compliance ReportDate: 2026-04-29 21:17:56 UTC ✅ All Tests Passed!30/30 tests passed Capture Tests✅ 29/29 tests passed View Details
Feature_Flags Tests✅ 1/1 tests passed View Details
|
Address PR feedback (mirrors posthog-python#539's 95eb1e9 fix). The /flags
response carries `errorsWhileComputingFlags` to signal partial-evaluation
failure, but events fired from both the existing single-flag path and the
new snapshot path were dropping it.
%PostHog.FeatureFlags.Result{} gains :errors_while_computing, populated from
the response body. log_feature_flag_usage/3 now builds a comma-joined
$feature_flag_error property when errors are present.
The snapshot path also fires $feature_flag_called for unknown flags with
$feature_flag_error: "flag_missing" — useful telemetry for "user asked for
a flag that does not exist" — combined with errors_while_computing_flags
when both apply ("errors_while_computing_flags,flag_missing"). The existing
single-flag path is unchanged for missing flags (still returns nil/error).
Generated-By: PostHog Code
Task-Id: c6aa804c-618a-4229-b6a3-dc8c9ccff778
Adds @deprecated to the four legacy single-flag entry points and their auto-generated lower-arity overloads, pointing users at evaluate_flags/2 + Evaluations. Compile-time warnings emit on every call site; functions continue to return the same values. Removal is planned for the next major. Each function also gains a "Deprecated" admonition in its @doc so generated HexDocs surface the migration guidance. Generated-By: PostHog Code Task-Id: c6aa804c-618a-4229-b6a3-dc8c9ccff778
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
One
/flagscall, multiple flag checks, plus event enrichment from the same snapshot — replacing Ncheck/3round-trips.PostHog.FeatureFlags.evaluate_flags/2returns anEvaluationssnapshot. Passflag_keys: [...]to scope the underlying request body (forwarded asflag_keys_to_evaluate).enabled?/2,get_flag/2,get_flag_payload/2,only/2,keys/1,event_properties/1.enabled?/2andget_flag/2fire$feature_flag_calledwith full metadata;get_flag_payload/2doesn't.set_in_context/2copies the snapshot's$feature/<key>and$active_feature_flagsinto the per-process context so subsequentPostHog.capture/3calls pick them up — no newcapturearity needed.%PostHog.FeatureFlags.Result{}now carries:id,:version,:reason,:request_id,:evaluated_at, and:errors_while_computing. Both paths attach the matching$feature_flag_*properties (including$feature_flag_error: "errors_while_computing_flags") when the response provides them. Snapshot path also fires$feature_flag_error: "flag_missing"for unknown keys, combined comma-style with the response-level error when both apply.check/3,check!/3,get_feature_flag_result/4, andget_feature_flag_result!/4are now@deprecated(compile-time warnings + admonitions in@doc). They continue to return the same values; removal is planned for the next major.RFC: https://github.com/PostHog/requests-for-comments-internal/pull/1020. Reference implementations: posthog-python#539, posthog-js#3476.
Design decisions
capture(flags: snapshot)argument. Elixir's SDK already propagates$feature/*through the per-process context thatcheck/3writes to andcapture/3reads back.set_in_context/2slots into that mechanism instead of fighting it.only/2(filter by explicit keys) is enough;only_accessed/1from the cross-SDK API is dropped.enabled?/2, notis_enabled/2. Idiomatic Elixir;is_*is reserved for guard-safe predicates and Credo flags it.build_result/3andlog_feature_flag_usage/3. Single source of truth for$feature_flag_calledproperties — including the rich metadata and the new$feature_flag_errorcodes.flag_missingevents; existing single-flag path keeps its{:error, ...}/{:ok, nil}contract for missing flags. Mirrors posthog-python's per-call telemetry from a snapshot while not changing existingcheck/3behavior.$feature_flag_called; both paths fire on every access. Adding the cache is a separate behavior change to existing call sites and lands in its own PR.only/2silently drops unknown keys (matchingMap.take/2), so there's no warning to silence.Out of scope (future PRs)
$feature_flag_calledevents, applied to both paths.locally_evaluated,$feature_flag_reason: "Evaluated locally", and$feature_flag_definitions_loaded_atplumbed through the snapshot.only_accessed/1if users ask for it.Created with PostHog Code